using System;
using System.Collections.Generic;
using System.Linq;
using Karma.Framework.Core.Domain;
using Karma.Framework.Core.Utils;

namespace Karma.Framework.Core.Metadata.Description
{
    public static class TypeDescriptor
    {
        private static IDictionary<string, EntityDescription> _entityDescriptions = new Dictionary<string, EntityDescription>();

        public static EntityDescription GetDescription(this Type type)
        {
            if (_entityDescriptions.ContainsKey(type.FullName))
            {
                return _entityDescriptions[type.FullName];
            }

            var description = new EntityDescription
                                  {
                                      Name = type.FullName
                                  };
            _entityDescriptions.Add(type.FullName, description);

            var allproperties = ReflectionUtils.GetProperties(type);
            description.Properties = (from p in allproperties
                                      where !typeof(IList<>).IsAssignableFrom(p.PropertyType) &&
                                            !(p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() != typeof(Nullable<>))
                                      let isEntity = typeof(DomainObject).IsAssignableFrom(p.PropertyType)
                                      let isEnum = p.PropertyType.IsEnum
                                      let tName = p.PropertyType.Name
                                      let isNullable = p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)
                                      orderby p.Name
                                      select new TypeDescription
                                                 {
                                                     Name = p.Name,
                                                     Type = isEnum ? p.PropertyType.FullName :
                                                        isNullable ? p.PropertyType.GetGenericArguments()[0].Name : tName,
                                                     IsEntity = isEntity,
                                                     Description = isEntity ? p.PropertyType.GetDescription() : null
                                                 }).ToList();
            description.Aggregates = (from p in allproperties
                                      where p.PropertyType.IsGenericType &&
                                            typeof(IList<>).IsAssignableFrom(p.PropertyType.GetGenericTypeDefinition())
                                      let tName = p.PropertyType.GetGenericArguments()[0].Name
                                      orderby p.Name
                                      select new AggregateDescription
                                                 {
                                                     Name = p.Name,
                                                     TargetType = p.PropertyType.GetGenericArguments()[0].Name,
                                                     Description = p.PropertyType.GetGenericArguments()[0].GetDescription()
                                                 }).ToList();
            description.Operations = (from o in type.GetMethods()
                                      where !o.Name.Equals("Equals") &&
                                            !o.Name.Equals("ToString") &&
                                            !o.Name.Equals("GetType") &&
                                            !o.Name.Equals("GetHashCode") &&
                                            !o.Name.StartsWith("get_") &&
                                            !o.Name.StartsWith("set_") &&
                                            !o.IsAbstract &&
                                            !o.IsStatic &&
                                            o.IsPublic
                                      orderby o.Name
                                      select new OperationDescription
                                                 {
                                                     Name = o.Name,
                                                     ReturnType = o.ReturnType.Name,
                                                     Parameters = (from p in o.GetParameters()
                                                                   let isEntity = typeof(DomainObject).IsAssignableFrom(p.ParameterType)
                                                                   let isEnum = p.ParameterType.IsEnum
                                                                   let tName = p.ParameterType.Name
                                                                   select new TypeDescription
                                                                              {
                                                                                  Name = p.Name,
                                                                                  Type = isEnum ? p.ParameterType.FullName : tName,
                                                                                  IsEntity = isEntity,
                                                                                  Description = isEntity ?
                                                                                                             p.ParameterType.GetDescription() : null
                                                                              }).ToList()
                                                 }).ToList();
            return description;
        }
    }
}